package com.liss.boot.redis.service;

import com.liss.boot.redis.entities.User;
import com.liss.boot.redis.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class UserService {
    public static final String CACHE_KEY_USER = "user:";

    @Resource
    private UserMapper userMapper;
    @Resource
    private RedisTemplate redisTemplate;

    public void addUser(User user) {
        int i = userMapper.insertSelective(user);
        if (i > 0) {
            user = userMapper.selectByPrimaryKey(user.getId());
            redisTemplate.opsForValue().set(CACHE_KEY_USER + user.getId(), user);
        }
    }
    public void delete(Integer id){
        int i = userMapper.deleteByPrimaryKey(id);
        if (i>0){
            redisTemplate.delete(CACHE_KEY_USER+id);
        }
    }
    public void update(User user){
        int i = userMapper.updateByPrimaryKeySelective(user);
        if (i>0){
            user = userMapper.selectByPrimaryKey(user.getId());
            redisTemplate.opsForValue().set(CACHE_KEY_USER + user.getId(), user);
        }
    }
    /**
     * 业务逻辑并没有写错，对于小厂中厂(QPS《=1000)可以使用，但是大厂不行
     * 理由：
     * 由于实际工作中，一般redis的key都是会被设置过期时间的。
     * 如果突然碰到过期时间到了，某个热点key突然失效了，就悲剧了。会发生redis中热点key突然失效导致的缓存击穿问题。
     * @param id
     * @return
     */
    public User findUserById(Integer id){
        User user = null;
        String key = CACHE_KEY_USER +id;

        user = (User) redisTemplate.opsForValue().get(key);
        if (user == null){
            user = userMapper.selectByPrimaryKey(id);
            if (user == null){
                return user;
            } else{
                redisTemplate.opsForValue().set(key,user);
            }
        }
        return user;
    }
    /**
     * 加强补充，避免突然key实现了，打爆mysql，做一下预防，尽量不出现击穿的情况。
     * @param id
     * @return
     */
    public User findUserById2(Integer id){
        User user = null;
        String key = CACHE_KEY_USER +id;

        //1 先从redis里面查询，如果有直接返回结果，如果没有再去查询mysql
        user = (User) redisTemplate.opsForValue().get(key);
        if (user == null){
            //2 大厂用，对于高QPS的优化，进来就先加锁，保证一个请求操作，让外面的redis等待一下，避免击穿mysql
            synchronized(UserService.class){
                user = (User) redisTemplate.opsForValue().get(key);
                //3 二次查redis还是null，可以去查mysql了(mysql默认有数据)
                if (user == null){
                    //4 查询mysql拿数据
                    user = userMapper.selectByPrimaryKey(id);
                    if (user == null){
                        return user;
                    } else{
                        //5 mysql里面有数据的，需要回写redis，完成数据一致性的同步工作
                        redisTemplate.opsForValue().setIfAbsent(key,user,7L, TimeUnit.DAYS);
                    }
                }
            }
        }
        return user;
    }
    /**
      几大类型应用场景
      String：比如抖音无限点赞某个视频或商品，点一下加一次
               是否喜欢的文章
      hash：JD购物车早期设计，目前已不再采用，当前小中厂可用
               新增商品——>hset shopcar:uid1024 334488 1
               新增商品——>hset shopcar:uid1024 334477 1
               增加商品数量——hincrby:shopcar:uid1024 334477 1
               商品总数——>hlen shopcar:uid1024
               全部选择——>hgetall shopcar:uid1024
      list:1、微信公众号订阅的消息
                大V作者李永乐老师和CSDN发布了文章分别是11和22
               关注了他们，只要发布新文章，就会安装进我的list：lpush likearticle:myid 11 22
               查看订阅的全部文章，类似分页，下面0~10就是一次显示10条：lrange likearticle:myid 0 10
           2、商品评论列表
               需求1：用户针对某一商品发布评论，一个商品会被不同的用户进行评论，保存商品评论时，要按时间顺序排序
               需求2：用户在前端页面查询该商品的评论，需要按照时间顺序降序排列
               case:使用list存储商品评论信息，key是该商品的id，value是商品评论信息商品编号为1001的商品评论key【items:comment:1001】
                   lpush items:comment:1001 {"goods json str"}
      set:集合运算
               A :sadd setA a b c 1 2  B：sadd setB 1 2 3 a x
               集合的差集运算A-B 属于A但不属于B的元素构成的集合 sdiff setA setB
               集合的交集运算A∩B 属于A同时属于B的元素共同构成的集合 sinter setA setB
               集合的并集运算A∪B 属于A或属于B的元素合并后构成的集合 sunion setA setB

               场景：
               微信抽奖小程序：1、用户ID，立即参与按钮 sadd key 用户id
                             2、显示已经有多少人参与 scard key
                             3、抽奖 srangmember key 2 随机抽奖2个人，元素不会删除
                                    spop key 3 随机抽奖3个人，元素会删除
               微信朋友圈点赞：1、新增点赞 sadd pub:msgID 点赞用户id1 点赞用户id2
                             2、取消点赞 srem pub:msgID 取消用户id
                             3、展现所有点赞的用户 smembers pub:msgID
                             4、点赞用户数统计，就是常见的点赞红色数字 scard pub:msgID
                             5、判断某个朋友是否对楼主点赞过 sismember pub:msgID 用户ID
               微博好友关注社交关系  共同关注的人 sinter setA setB
                                   我关注的人也关注他（大家爱好相同）sismember setA 3 sismember setB 3
               QQ内推可能认识的人   sinter s1 s2  sdiff s1 s2 sdiff s2 s1

      zset：根据商品销售对商品进行排序显示  key为goods:sellsort，分数为商品销售数量
                   商品编号为1001的销售是9，商品编号1002的销量是15  zadd goods:sellsort 9 1001 15 1002
                   有一个客户又买了2件商品1001，商品编号1001销量加2 zincrby goods:sellsort 2 1001
                   求商品销量前10名  zrange goods:sellsort 0 9 withscores
            抖音热搜
                   点击视频    zincrby  hotcvr:20210913 1 八百
                              zincrby  hotcvr:20210913 15 八百 5 花木兰
                   展示当日排行前10条 zrevrange hotcvr:20210913 0 9 withscores

      案例实战02：微信文章阅读量统计
                   if(num>10w){return;}else {incriby k1;}
     */

    /**
     亿级系统常见的四种统计
          聚合统计：统计多个集合元素的聚合结果，就是前面讲解过的交叉并等集合统计；交并叉集和聚合函数的应用
          排序统计：抖音视频最新评论留言的常见（排序——分页显示）
                       在面对需要展示最新列表、排行榜等场景时，如果数据更新频繁或需要分页显示，建议使用zset：获取元素排名  从小到大 zrank key member  从大到小  zrevrank key member
           二值统计：集合元素的取值就只有0和1两种。（签到/未签到）bitmap
           基数统计：只统计一个集合中不重复元素的个数   hyperloglog

      bitmap：由0和1状态表现的二进制位的bit数组
                   用于状态统计   Y、N 类似AtomicBoolean
                   需求：用户是否登录Y、N，比如京东每日签到送京豆
                        电影、广告是否被点击播放过
                        打卡上下班、签到统计
                   真实案例：
                        日活统计
                        连续签到打卡
                        最近一周的活跃用户
                        统计指定用户一年之中的登录天数
                        某用户按照一年365天，哪几天登陆过？哪几天没登陆过？全年中登录的天数共计多少？
                    京东登录送京豆
                           小厂方法，传统MySQL
                           大厂方法，基于redis的Bitmaps实现签到日历
              方法：
                   setbit ：setbit 键偏移位 只能0或者1；bitmap的偏移量都是从零开始算的
                   getbit： getbit
                   bitcount：bitcount
     */
}
